#include <WiFi.h>
#include <WiFiClient.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include "DHT.h" //加载DHT11的库
#include "esp_http_client.h"
#include "esp_camera.h"
#include "configuration.h"
#include "camera_pins.h"

char config_flag = 0;   //配网成功标识
//tcp客户端相关
String TcpClient_Buff = "";
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;//心跳
unsigned long preTCPStartTick = 0;//连接
String over_int = "false";
bool preTCPConnected = false;
String UID = "c124bafd03ba4909b1f7794c3e38af32";    //巴法云私钥
String TOPIC = "mytrash";   //主题名字
//图像拍照上传相关
int capture_interval = 5 * 1000;                       // 20秒上传一次
const char* uid = "c124bafd03ba4909b1f7794c3e38af32";  //私钥
const char* topic = "mytrash";                       //主题
const char* wechatMsg = "";                            //如果不为空，会推送到微信
const char* wecomMsg = "";                             //如果不为空，会推送到企业微信，推送到企业微信的消息
const char* urlPath = "";                              //如果不为空，会生成自定义图片链接，自定义图片上传后返回的图片url，url前一部分为巴法云域名，第二部分：私钥+主题名的md5值，第三部分为设置的图片链接值。
bool flashRequired = true;                            //闪光灯，true是打开闪光灯
const int brightLED = 4;                               //闪光灯引脚
const int sensorPin = 14; // 溢出检测引脚
unsigned char sendp = 0;
int sensorValue = 0;
int connect_time = 0;

const char* post_url = "http://images.bemfa.com/upload/v1/upimages.php";  // 默认上传地址
static String httpResponseString;                                         //接收服务器返回信息
bool internet_connected = false;
long current_millis;
long last_capture_millis = 0;

// Bright LED (Flash)
const int ledFreq = 50;        // PWM settings
const int ledChannel = 15;     // camera uses timer1
const int ledRresolution = 8;  // resolution (8 = from 0 to 255)

struct config_type
{
  char stassid[32];
  char stapsw[64];
   char cuid[40];
   char ctopic[32];
  uint8_t reboot;
  uint8_t magic;
};
config_type config;

WiFiClient TCPclient;
Ticker delayTimer;
DHT dht(DHTPIN, DHTTYPE);//声明 dht 函数

/**
* 存储配网信息
*/
void saveConfig()
{
  int rand_key;
  uint8_t mac[6];
  WiFi.macAddress(mac);
  config.reboot = 0;
  EEPROM.begin(512);
  uint8_t *p = (uint8_t*)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}
 
void delayRestart(float t) 
{
  delayTimer.attach(t, []() 
  {
    ESP.restart();
  });
}

/**
* airkiss配网
*/
void doSmartconfig()
{
  Serial.println("waiting for WeChat Config.....");
  Serial.print("*");
  Serial.print("*");
  Serial.print("*");
  Serial.print("*");
  Serial.print("*");
  Serial.println("*");
  WiFi.mode(WIFI_STA);
  WiFi.stopSmartConfig();
  WiFi.beginSmartConfig();
  int cnt = 0;
  bool flag_ok = false;
  while (!WiFi.smartConfigDone()) 
  {
    delay(300);
    if (flag_ok == true) continue;
    if (WiFi.smartConfigDone()) 
    {
        strcpy(config.stassid, WiFi.SSID().c_str());
        strcpy(config.stapsw, WiFi.psk().c_str());
        config.magic = MAGIC_NUMBER;
        saveConfig();
        flag_ok = true; 
    }
    cnt++;
    if (cnt >= 600) 
    {
      delayRestart(0);
    }
  }
}

/**
* 设置SmartConfig
*/
void setConfig()
{
    String mac = WiFi.macAddress().substring(8);//取mac地址做主题用
    mac.replace(":", "");//去掉:号
    WiFiClient client_bemfa_WiFiClient;
    HTTPClient http_bemfa_HTTPClient;
    http_bemfa_HTTPClient.begin(client_bemfa_WiFiClient,"http://pro.bemfa.com/vv/setSmartConfig?version=1&user="+mac);
    int httpCode = http_bemfa_HTTPClient.GET();
    if (httpCode == 200) 
    {
      Serial.println("wifi smartconfig ok");
    }
    http_bemfa_HTTPClient.end();
}

/**
* 初始化wifi信息，并连接路由器网络
*/
void initWiFi()
{
  char temp[32];
  uint8_t mac[6];
  WiFi.macAddress(mac);
  //sprintf(temp, "%s_%02X%02X%02X", HOST_NAME, mac[3], mac[4], mac[5]);
  WiFi.hostname(temp);
  if(WiFi.status() != WL_CONNECTED)
  {
    WiFi.disconnect();//断开连接
    WiFi.mode(WIFI_STA);//STA模式
    WiFi.begin(config.stassid, config.stapsw);//连接路由器
  }
  Serial.print("Waiting for connect");
  while (WiFi.status() != WL_CONNECTED) 
  {//检查是否连接成功
    delay(500);
    Serial.print(".");
    connect_time++;
    if(connect_time >= 360)
    {
      connect_time = 0;
      Serial.println("wifi connect faild");
    }
  }
  if(config_flag == 1)
  {
    setConfig();
  }
  Serial.println("wifi is connected");
  //Serial.print("ssid:");
  //Serial.println(WiFi.SSID());
  //Serial.print("psw:");
  //Serial.println(WiFi.psk());
  WiFi.softAP(temp);
}

/**
* 加载存储的信息，并检查是否进行了反复5次重启恢复出厂信息
*/
uint8_t *p = (uint8_t*)(&config);
void loadConfig()
{
    uint8_t mac[6];
    WiFi.macAddress(mac);
    EEPROM.begin(512);
    for (int i = 0; i < sizeof(config); i++)
    {
      *(p + i) = EEPROM.read(i);
    }
    config.reboot = config.reboot + 1;
    if(config.reboot>=4)
    {
        restoreFactory();
    }
    if(config.magic != MAGIC_NUMBER)
    {
      config_flag = 1;
    }
    EEPROM.begin(512);
    for (int i = 0; i < sizeof(config); i++)
    {
      EEPROM.write(i, *(p + i));
    }
    EEPROM.commit();
    delay(2000);
    EEPROM.begin(512);
    config.reboot = 0;
    for (int i = 0; i < sizeof(config); i++)
    {
      EEPROM.write(i, *(p + i));
    }
    EEPROM.commit();
    delay(2000);
}

/**
* 恢复出厂设置，清除存储的wifi信息
*/
void restoreFactory(){
    Serial.println("Restore Factory.......");
    config.magic = 0x00;
    strcpy(config.stassid, "");
    strcpy(config.stapsw, "");
    config.magic = 0x00;
    saveConfig();
    delayRestart(1);
    while (1) 
    {
      delay(100);
    }
 }

/**
* 检查是否需要airkiss配网
*/
void waitKey()
{
  if(config_flag == 1)
  {
    doSmartconfig();
  }
}

/*
  *发送数据到TCP服务器
 */
void sendtoTCPServer(String p)
{
  if (!TCPclient.connected()) 
  {
    //Serial.println("Client is not readly");
    return;
  }
  TCPclient.print(p);
  //Serial.println("[Send to TCPServer]:String");
}

/*
  *初始化和服务器建立连接
*/
void startTCPClient()
{
  if(TCPclient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT)))
  {
    //Serial.print("\nINFO:Connected to server:");
    //Serial.printf("%s:%d\r\n",TCP_SERVER_ADDR,atoi(TCP_SERVER_PORT));
    preTCPConnected = true;
    preHeartTick = millis();
    TCPclient.setNoDelay(true);
    sendtoTCPServer("cmd=1&uid="+UID+"&topic="+TOPIC+"\r\n");
  }
  else
  {
    //Serial.print("INFO:Failed connected to server:");
    //Serial.println(TCP_SERVER_ADDR);
    TCPclient.stop();
    preTCPConnected = false;
  }
  preTCPStartTick = millis();
}

/*
  *检查数据，发送数据
*/
void doTCPClientTick()
{
 //检查是否断开，断开后重连
  if(WiFi.status() != WL_CONNECTED) return;
  if (!TCPclient.connected()) 
  {//断开重连
    if(preTCPConnected == true)
    {
      preTCPConnected = false;
      preTCPStartTick = millis();
      Serial.println("TCP Client disconnected");
      TCPclient.stop();
    }
    else if(millis() - preTCPStartTick > 1*1000)//重新连接
      startTCPClient();
  }
  else
  {
    if (TCPclient.available()) 
    {//收数据
      char c =TCPclient.read();
      TcpClient_Buff +=c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();
      if(TcpClient_BuffIndex>=MAX_PACKETSIZE - 1)
      {
        TcpClient_BuffIndex = MAX_PACKETSIZE-2;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
      preHeartTick = millis();
    }
    if(millis() - preHeartTick >= upDataTime)
    {//上传数据
      preHeartTick = millis();
      float h = dht.readHumidity();//读取湿度
      float t = dht.readTemperature();//读取摄氏度
      if (isnan(h) || isnan(t)) 
      {
        Serial.println("Failed to read from DHT sensor!");
        return;
      } //检查抓取是否成功
      /*********************数据上传*******************/
      String upstr = "";
      String uart_str = "";
      upstr = "cmd=2&uid="+UID+"&topic="+TOPIC+"&msg=#"+over_int+"#"+t+"#"+h+"\r\n";
      sendtoTCPServer(upstr);
      uart_str = "DATA#"+over_int+"#"+t+"#"+h+"\r\n";
      Serial.print(uart_str);
      upstr = "";
    }
  }
  if((TcpClient_Buff.length() >= 1) && (millis() - TcpClient_preTick>=200))
  {//data ready
    TCPclient.flush();
    //Serial.print("Buff:");
    //Serial.println(TcpClient_Buff);
    TcpClient_Buff="";
    TcpClient_BuffIndex = 0;
  }
}

// change illumination LED brightness
void brightLed(byte ledBrightness) {
  ledcWrite(ledChannel, ledBrightness);  // change LED brightness (0 - 255)
}

// ----------------------------------------------------------------
//       set up PWM for the illumination LED (flash)
// ----------------------------------------------------------------
// note: I am not sure PWM is very reliable on the esp32cam - requires more testing
void setupFlashPWM() {
  ledcSetup(ledChannel, ledFreq, ledRresolution);
  ledcAttachPin(brightLED, ledChannel);
  brightLed(0);
}

void Setint(){
  sendp = 1;
}

void setup() {
  Serial.begin(115200);
  pinMode(brightLED, OUTPUT);    // flash LED
  pinMode(sensorPin, INPUT); // 将引脚设置为输入模式
  digitalWrite(brightLED, LOW);  // led off = Low
  attachInterrupt(2, Setint, FALLING);  //当电平发生变化时，触发中断
  attachInterrupt(13, restoreFactory, FALLING);  //当电平发生变化时，触发中断
  dht.begin(); //启动传感器
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.frame_size = FRAMESIZE_UXGA;
  config.pixel_format = PIXFORMAT_JPEG;  // for streaming
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 10;
  config.fb_count = 1;

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if (config.pixel_format == PIXFORMAT_JPEG) {
    if (psramFound()) {
      config.jpeg_quality = 10;
      config.fb_count = 2;
      config.grab_mode = CAMERA_GRAB_LATEST;

    } else {
      // Limit the frame size when PSRAM is not available
      config.frame_size = FRAMESIZE_SVGA;
      config.fb_location = CAMERA_FB_IN_DRAM;
    }
  } else {
    // Best option for face detection/recognition
    config.frame_size = FRAMESIZE_240X240;
#if CONFIG_IDF_TARGET_ESP32S3
    config.fb_count = 2;
#endif
  }

#if defined(CAMERA_MODEL_ESP_EYE)
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
#endif

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    //Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t* s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1);        // flip it back
    s->set_brightness(s, 1);   // up the brightness just a bit
    s->set_saturation(s, -2);  // lower the saturation
  }


#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);
#endif

#if defined(CAMERA_MODEL_ESP32S3_EYE)
  s->set_vflip(s, 1);
#endif

  loadConfig();
  waitKey();
  initWiFi();
  //Serial.print("Camera Ready!:");
  //Serial.println(WiFi.localIP());
  setupFlashPWM();  // configure PWM for the illumination LED
}

/********http请求处理函数*********/
esp_err_t _http_event_handler(esp_http_client_event_t* evt) {
  if (evt->event_id == HTTP_EVENT_ON_DATA) {
    httpResponseString.concat((char*)evt->data);
  }
  return ESP_OK;
}

static esp_err_t take_send_photo() {
  if (flashRequired) {
    brightLed(255);
    delay(300);
  }
  //Serial.println("take_send_photo...");
  camera_fb_t* fb = NULL;
  esp_err_t res = ESP_OK;
  fb = esp_camera_fb_get();
  if (flashRequired) brightLed(0);  // change LED brightness back to previous state

  if (!fb) {
    //Serial.println("Camera capture failed...");
    return ESP_FAIL;
  }

  httpResponseString = "";
  esp_http_client_handle_t http_client;
  esp_http_client_config_t config_client = { 0 };
  config_client.url = post_url;
  config_client.event_handler = _http_event_handler;
  config_client.method = HTTP_METHOD_POST;
  http_client = esp_http_client_init(&config_client);
  esp_http_client_set_post_field(http_client, (const char*)fb->buf, fb->len);  //设置http发送的内容和长度
  esp_http_client_set_header(http_client, "Content-Type", "image/jpg");        //设置http头部字段
  esp_http_client_set_header(http_client, "Authorization", uid);               //设置http头部字段
  esp_http_client_set_header(http_client, "Authtopic", topic);                 //设置http头部字段
  esp_http_client_set_header(http_client, "wechatmsg", wechatMsg);             //设置http头部字段
  esp_http_client_set_header(http_client, "wecommsg", wecomMsg);               //设置http头部字段
  esp_http_client_set_header(http_client, "picpath", urlPath);                 //设置http头部字段
  esp_err_t err = esp_http_client_perform(http_client);                        //发送http请求
  if (err == ESP_OK) {
    //json数据解析
    StaticJsonDocument<200> doc;
    DeserializationError error = deserializeJson(doc, httpResponseString);
    if (error) {
      //Serial.print(F("deserializeJson() failed: "));
      //Serial.println(error.c_str());
    }
  }
  //Serial.println("Taking picture END");
  esp_camera_fb_return(fb);
  esp_http_client_cleanup(http_client);
  return res;
}

void loop() 
{
  sensorValue = digitalRead(sensorPin); // 读取引脚的电平
  if (sensorValue == HIGH) 
  {
    over_int = "true";
  } 
  else
  {
    over_int = "false";
  }
  doTCPClientTick();
  // current_millis = millis();
  // if (current_millis - last_capture_millis > capture_interval) 
  // {
  //   last_capture_millis = millis();
  //   take_send_photo();
  // }
  if (sendp == 1) 
  {  // Take another picture
    take_send_photo();
    sendp = 0;
  }
}
